package com.hoopchina.service;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.util.List;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import com.hoopchina.util.UrlUtils;

import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Environment;

/**
 * Class for self-updating of application. Singleton class which uses MainActivity as context.
 * @author Si Wei
 *
 */
public class UpdateService {
	private static UpdateService  mInstance = null;               // static instance for singleton
	private Activity              mContext;                       // UI context for getting service
	private int                   mVersionCode;                   // current version code
	private String                mVersionName;                   // current version name
	private int                   mLatestVersionCode   = 0;       
	private String                mLatestVersionName   = "0.0";
	private String                mUpdates;
	private String                mPackageName;                   // current package name
	private String                mNewPackageUrl       = "";  
	private boolean               mNewVersionExists;
	private final String          mDownloadFileName    = "update.apk";
	private final int             mBufferSize          = 1024;
	private long                  mPackageSize;                   // total size of new package
	private long                  mDownloadedSize;                // downloaded sized
	private boolean               mIsDownloading;                 // downloading flag
	
    /**
     * Singleton protal
     * @return LBSService instance
     */
	public static UpdateService Instance() {
		if ( mInstance == null ) {
			mInstance = new UpdateService();
		}
		return mInstance;
	}
	
	/**
	 * Private constructor
	 */
	private UpdateService() {
		deleteDownloadedFile();
	}
	
	public void setContext ( Activity aContext ) {
		mContext = aContext;
		checkAppVersion();
	}

	/**
	 * Check application version versus latest version from server update
	 * @param aContext
	 */
	public void check() {
		if ( isNetworkAvailable() ) {
			getAndCompareVersion();
		}
	}
	
	/**
	 * @return if new version exists on server
	 */
	public boolean newVersionExists() {
		return mNewVersionExists;
	}
	
	/**
	 * 
	 * @return current version name
	 */
	public String getVersionName() {
		return mVersionName;
	}
	
	/**
	 * 
	 * @return latest version name
	 */
	public String getLatestVersionName() {
		return mLatestVersionName;
	}
	
	/**
	 * 
	 * @return package total size
	 */
	public long getPackageSize() {
		return mPackageSize;
	}
	
	/**
	 * 
	 * @return downloaded size
	 */
	public long getDownloadedSize() {
		return mDownloadedSize;
	}
	
	/**
	 * 
	 * @return
	 */
	public String getUpdatesText() {
		return mUpdates;
	}
	
	/**
	 * 
	 * @return if network is OK to use
	 */
	public boolean isNetworkAvailable() {
        try {
        	ConnectivityManager connMgr = 
        		( ConnectivityManager )mContext.getSystemService( Context.CONNECTIVITY_SERVICE );   
            NetworkInfo netInfo = connMgr.getActiveNetworkInfo();   
            return ( netInfo != null && netInfo.isConnected() );   
        } catch ( Exception e ) {   
            e.printStackTrace();   
            return false;
        }  
	}
	
	/**
	 * download package to local disk
	 * @throws Exception 
	 */
	public void download() throws Exception {
		if ( !mNewPackageUrl.equals( "" ) && !mIsDownloading ) {
			mIsDownloading = true;
			mDownloadedSize = 0;
			mPackageSize = 0;
			try {
				downloadPackage();
			}
			catch ( Exception e ) {
				// exception could be network problem or IO issue, i.e. creating file failed
				// we have to reset the flag or it's not possible to download again
				mIsDownloading = false;
				throw e; // throw up exception, let UI handle this
			}
			mIsDownloading = false;
		}
		else {
			// xml was not well parsed, recognize this as exception
			throw new Exception();
		}
	}
	
	/**
	 * install downloaded package
	 */
	public void install( boolean aKillCurrent ) {
		File downloadFile = getDownloadFile();
		if ( downloadFile.exists() && mIsDownloading ) {
			// there's no downloaded package
			return;
		}
		
		if ( aKillCurrent ) {
			killRunningProcess();
		}
		
		installPackage( downloadFile );
	}
	
	/**
	 * Delete old donwloaded file if necessary
	 */
	private void deleteDownloadedFile() {
		File file = getDownloadFile();
		if ( file.exists() ) {
			file.delete();
		}
	}
	
	/**
	 * get download file object
	 * @return file object
	 */
	private File getDownloadFile() {
		// path = //sdcard/_downloadFileName
		return new File( Environment.getExternalStorageDirectory(), mDownloadFileName );
	}

	/**
	 * Check current application version
	 */
	private void checkAppVersion() {
		mPackageName = mContext.getPackageName();
		try {
			PackageInfo info = mContext.getPackageManager().getPackageInfo( mPackageName, 0 );
			mVersionCode = info.versionCode;
			mVersionName = info.versionName;
		} catch (NameNotFoundException e) {
			e.printStackTrace();
		} 
	}
	
	/**
	 * Get latest version and compare to current<br/><br/>
	 * XML file format: [package][url/][versioncode/][versionname/][/package]
	 */
	private void getAndCompareVersion() {
		// get version number and package url first
		try {
            // using SAX parse this small xml file
            SAXParserFactory saxFactory=SAXParserFactory.newInstance();  
            saxFactory.setNamespaceAware( true );  
            saxFactory.setValidating( false );  
            SAXParser parser = saxFactory.newSAXParser();  
            InputStream inputStream = Networking.getStreamFromUrl( UrlUtils.UrlUpdate );
            parser.parse( inputStream, new DefaultHandler(){  
                private String currentTag = "";
                
                @Override
                public void startDocument() throws SAXException {
					super.startDocument();
                }
                
                @Override  
                public void startElement(String uri, String localName,
                        String qName, org.xml.sax.Attributes attributes)
                        throws SAXException {
                	currentTag = localName;
                }
                
                @Override
                public void characters(char[] ch, int start, int length)
                        throws SAXException {
                    String value = new String( ch, start, length ).trim();
                    if ( value == null || value.equals( "" ) )
                        return;
                    if ( currentTag.equals( "url" ) ) {
                        mNewPackageUrl = value;
                    }
                    if ( currentTag.equals( "updates" ) ) {
//                         String[] updateTexts = value.split( "\\n" );
//                         StringBuilder sb = new StringBuilder();
//                         for ( String updateText : updateTexts ) {
//                        	 sb.append( updateText );
//                        	 sb.append( "\n" );
//                         }
//                    	_updates = sb.toString();
                    	mUpdates = value;
                    }
                    if ( currentTag.equals( "versioncode" ) ) {
                        mLatestVersionCode = Integer.valueOf( value );
                    }
                    if ( currentTag.equals( "versionname" ) ) {
                        mLatestVersionName = value;
                    }
                }
                  
                @Override
                public void endElement(String uri, String localName,
                        String qName) throws SAXException {
                    super.endElement( uri, localName, qName );
                }
                  
                @Override
                public void endDocument() throws SAXException {
                    super.endDocument();
                }
            });  
        } catch ( MalformedURLException e ) {  
            e.printStackTrace();  
        } catch ( ParserConfigurationException e ) {  
            e.printStackTrace();  
        } catch ( SAXException e ) {  
            e.printStackTrace();  
        } catch ( IOException e ) {  
            e.printStackTrace();  
        }
        
        // if ran down here then congratulations to us, no error at all, ready to compare versions
        if ( mLatestVersionCode > mVersionCode ) {
        	// there's new version on the server
        	mNewVersionExists = true;
        }
	}
	
	/**
	 * Download package
	 */
	private void downloadPackage() throws IOException, ClientProtocolException {
        HttpResponse response;
        try {  
        	response = Networking.getResponseFromUrl( mNewPackageUrl );
            HttpEntity entity = response.getEntity();
            mPackageSize = entity.getContentLength();
            InputStream is = entity.getContent();
            FileOutputStream fileOutputStream = null;
            if (is != null) {
            	deleteDownloadedFile();
            	File newFile = getDownloadFile();
            	if ( !newFile.createNewFile() ) {
            		throw new IOException();
            	}
                fileOutputStream = new FileOutputStream( newFile );
                byte[] buf = new byte[ mBufferSize ];
                int ch = -1;
                while ( ( ch = is.read( buf ) ) != -1 ) {
                	try {
						Thread.sleep( 50 );
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
                    fileOutputStream.write( buf, 0, ch );
                    mDownloadedSize += ch;
                }
            }
            fileOutputStream.flush();
            if ( fileOutputStream != null ) {
                fileOutputStream.close();
            }
        } catch ( ClientProtocolException e) {
            throw e;
        } catch ( IOException e ) {
            throw e;
        }
	}
	
	/**
	 * Kill current running process
	 */
	private void killRunningProcess() {
		ActivityManager activityMgr = 
			(ActivityManager)mContext.getSystemService( Context.ACTIVITY_SERVICE );  
		List<RunningAppProcessInfo> processes = activityMgr.getRunningAppProcesses();
        
        for ( RunningAppProcessInfo process : processes ) {
        	if ( process.processName.equals( mPackageName ) ) {
        		android.os.Process.killProcess( process.pid ); 
        		break;
        	}
        }
	}
	
	/**
	 * Install downloaded package
	 */
	private void installPackage( File aFile ) {
	    Intent intent = new Intent( Intent.ACTION_VIEW );
	    intent.setDataAndType( Uri.fromFile( aFile ), "application/vnd.android.package-archive" );  
	    mContext.startActivity(intent);
	}

}
